package com.perfectionism.mininotes.utility;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;

import com.perfectionism.mininotes.MiniNotes;
import com.perfectionism.mininotes.R;

/**
 * TouchListView是自定义的ListView类，并且有三个自定义
 * 的属性(attrs.xml中)。
 *
 * 这个类提供了长按一条item可以排序的功能。
 * 实现方法：
 * 1.onInterceptTouchEvent截获触摸事件，给触摸的item加上
 * OnLongClickListener监听器，在其中进行拖动前的一些初始
 * 化工作，并调用startDragging函数准备开始拖动。
 * 2.触摸事件会由函数onInterceptTouchEvent转交给onTouchEvent
 * 函数，在此函数中判断若事件是ACTION_MOVE，则调用dragView等
 * 方法开始移动，移动中还调用了doExpansion方法，该方法用来
 * 实现移动item的时候，其他item的让位效果
 * @author wangchen
 *
 */
public class TouchListView extends ListView {

	private ImageView mDragView;						// 拖拽项的影像
	private WindowManager mWindowManager;				// windows窗口控制类
	private WindowManager.LayoutParams mWindowParams;	// 用于控制拖拽项的显示的参数
	

	private int mDragPos; 			// 正在拖动的item的pos
	private int mFirstDragPos; 	// 被拖动前的pos
	
	// 被向左划的item，如果没有则为-1
	private int mItemSlideLeft = -1;
	// 防止正在滑动的时候再点击右滑动
	private boolean mIsSliding = false;
	
	// mDragPoint和mCoordOffset都仅是Y坐标的值
	// mDragPoint:拖动时所按点相对于列表上方的位置
	// mCoordOffset:拖动时所拖动的item相对与屏幕上方的位置
	private int mDragPoint; 		
	private int mCoordOffset; 		
	
	// mUpperBound:拖动的时候，开始向上滚动的边界
	// mLowerBound:拖动的时候，开始向下滚动的边界
	private int mUpperBound;		
	private int mLowerBound;		
	
	//在滑动的时候，手的移动要大于这个返回的距离值才开始移动控件
	private final int mTouchSlop;
	
	private DragListener mDragListener;
	private DropListener mDropListener;
	private GestureDetector mGestureDectector;


	// 被拖动item的宽度
	private int mHeight;
	private Rect mTempRect = new Rect();
	private Bitmap mDragBitmap;


	// 下面三个是自定义的属性，可以在xml里赋值
	// mDragBackgroundColor:拖动时的item的布局的背景
	// mItemHeightNormal:除了被拖动item外其他的高度
	// mItemHeightExpanded:拖动时为了显示让位效果，需要让位的item的高度
	// 一条item的高度，一般为mItemHeightNormal*2
	private int mDragBackgroundColor = 0x00000000;
	private int mItemHeightNormal = 0;
	private int mItemHeightExpanded = 0;
	
	public TouchListView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}
	// 截获触摸事件，当有触摸事件发生时先调用这个函数
	public TouchListView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
		// 获取用户设置的自定义的属性值
		if (attrs != null) {
			TypedArray a = getContext().obtainStyledAttributes(attrs,R.styleable.TouchListView, 0, 0);
			mItemHeightNormal = a.getDimensionPixelSize(R.styleable.TouchListView_normal_height, 0);
			mItemHeightExpanded = a.getDimensionPixelSize(R.styleable.TouchListView_expanded_height,mItemHeightNormal);
			mDragBackgroundColor= a.getColor(R.styleable.TouchListView_drag_background, 0x00000000);
			a.recycle();
		}
		// 对滑动事件的处理
		if(mGestureDectector==null) {
			mGestureDectector = new GestureDetector(new SimpleOnGestureListener(){
				@Override
				public boolean onFling(MotionEvent e1, MotionEvent e2,float velocityX, float velocityY) {
					if(mDragView!=null)
						return false;
					int x1 = (int)e1.getX();
					int y1 = (int)e1.getY();
					// 得到两个点所在的item的position
					final int downItemPos = pointToPosition(x1, y1);
					if(downItemPos!=AbsListView.INVALID_POSITION){
						final ViewGroup item =  (ViewGroup) getChildAt(downItemPos-getFirstVisiblePosition());
						// 向右划删除便笺
						if(e2.getX()>e1.getX()) {
							mIsSliding = true;
							final Animation animRemove = AnimationUtils.loadAnimation(getContext(), R.anim.push_right_out);
							final Animation animBack =  AnimationUtils.loadAnimation(getContext(), R.anim.push_right_in);
							animRemove.setAnimationListener(new AnimationListener() {
								public void onAnimationStart(Animation animation) {}
								public void onAnimationRepeat(Animation animation) {}
								public void onAnimationEnd(Animation animation) {
									if(!mDropListener.delete(downItemPos))
										item.startAnimation(animBack);
									mIsSliding = false;
								}
							});
							if(mDropListener.isAskBeforeDelete()){
								AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
								builder.setMessage("确定要删除便笺?");
								builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
									public void onClick(DialogInterface dialog, int which) {
										item.startAnimation(animRemove);
									}
								});
								builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
									public void onClick(DialogInterface dialog, int which) {
										mIsSliding = false;
										dialog.dismiss();
									}
								});
								builder.create().show();
							}
							else
								item.startAnimation(animRemove);
							return true;
						}
						// 向左划出现四个选项
						if(e1.getX()>e2.getX()&&!mIsSliding){
							Animation anim = AnimationUtils.loadAnimation(getContext(), R.anim.slide_left);
							anim.setFillEnabled(true);
							anim.setFillAfter(true);
							anim.setAnimationListener(new AnimationListener() {
								public void onAnimationStart(Animation animation) {
									mIsSliding = true;
								}
								public void onAnimationRepeat(Animation animation) {}
								public void onAnimationEnd(Animation animation) {
									mIsSliding = false;
									mItemSlideLeft = downItemPos;
									ImageView edit = (ImageView)item.findViewById(R.id.list_option_edit);
									ImageView bgColor = (ImageView)item.findViewById(R.id.list_option_bgcolor);
									ImageView clock = (ImageView)item.findViewById(R.id.list_option_clock);
									edit.setClickable(true);
									bgColor.setClickable(true);
									clock.setClickable(true);
									OnClickListener clickListener = new OnClickListener(){
										public void onClick(View v) {
											int id = v.getId();
											if(id==R.id.list_option_bgcolor){
												mItemSlideLeft = downItemPos;
												mDropListener.handleOptions(MiniNotes.OPTION_BGCOLOR, downItemPos);
											}else if(id==R.id.list_option_edit){
												slideRight();
												mDropListener.handleOptions(MiniNotes.OPTION_EDIT, downItemPos);
											}else if(id==R.id.list_option_clock){
												slideRight();
												mDropListener.handleOptions(MiniNotes.OPTION_CLOCK, downItemPos);
											}
										}
									};
									edit.setOnClickListener(clickListener);
									bgColor.setOnClickListener(clickListener);
									clock.setOnClickListener(clickListener);
								}
							});
							ViewGroup itemShouldMove = (ViewGroup)item.getChildAt(1);
							itemShouldMove.startAnimation(anim);
						}
						return true;
					}
					return false;
				}
			});
		}
		
		
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// 仅在当前没有item被划到左边时才解析手势
		if(mGestureDectector!=null&&mItemSlideLeft==-1){
			mGestureDectector.onTouchEvent(ev);
		}
		
		// 对长按事件的初步处理
		if (mDragListener != null || mDropListener != null) {
			switch (ev.getAction()) {
			case MotionEvent.ACTION_DOWN:
				//ev.getX()相对于控件本身左上角，ev.getRawX()相对于手机屏幕左上角位置
				int x = (int) ev.getX();
				final int y = (int) ev.getY();
				// 选中的数据项位置，使用ListView自带的pointToPosition(x, y)方法
				final int itemnum = pointToPosition(x, y);
				
				// 首先判断如果当前有item被滑到了左边，则先将它恢复过来
				if(mItemSlideLeft!=-1&&!mIsSliding&&itemnum!=mItemSlideLeft){
					slideRight();
				}
				
				// 如果是无效位置(超出边界，分割线等位置)，返回
				if (itemnum == AdapterView.INVALID_POSITION) {
					break;
				}
				// getChildAt方法只会返回可见的view，比如屏幕上现可见6个view，则getChildAt(0)
				// 返回第一个，因此itemnum-getFirstVisiblePosition()可得到需要的view的序号。
				final ViewGroup item =  (ViewGroup) getChildAt(itemnum-getFirstVisiblePosition());
				
				// 这两个参数用于后面拖动的开始位置和移动位置的计算
				// 这两个参数都是Y的坐标值
				// mDragPoint:拖动时所按点相对于列表上方的位置
				// mDragOffset:拖动时所拖动的item相对与屏幕上方的位置
				mDragPoint = y - item.getTop();
				mCoordOffset = ((int) ev.getRawY()) - y;
				
				// 设置item的长按事件监听器，在onLongClick做拖动前的初始化工作，
				// 在onTouchEvent里的ACTION_MOVE里做拖动的操作
				item.setOnLongClickListener(new OnLongClickListener() {
					public boolean onLongClick(View v) {
						if(mIsSliding||mItemSlideLeft!=-1){
							item.setOnLongClickListener(null);
							return true;
						}
						item.setDrawingCacheEnabled(true);
						// Create a copy of the drawing cache so that it does not get 
						// recycled by the framework when the list tries to clean up memory
						Bitmap bitmap = Bitmap.createBitmap(item.getDrawingCache());
						// bitmap是当前item的画面，传给startDragging函数里的ImageView
						startDragging(bitmap, y);
						mDragPos = itemnum;
						mFirstDragPos = mDragPos;
						mHeight = getHeight();
						int touchSlop = mTouchSlop;
		
						mUpperBound = Math.min(y - touchSlop, mHeight / 3);
						mLowerBound = Math.max(y + touchSlop, mHeight * 2 / 3);
						return true;
					}
				});
				item.setOnClickListener(new OnClickListener() {
					public void onClick(View v) {
						// 触发了滑动事件就不要触发发点击事件
						if(!mIsSliding&&mItemSlideLeft==-1) {
							mDropListener.displayNote(itemnum);
						}
					}
				});
				mDragView = null;
				break;
			/* 
			 * onLongClick处理了长按事件后,如果用户不进行拖动就松手，
			 * 那么onTouchEvent不会捕捉到任何事件，所以必须在这处理
			 * 用户长按但是还没移动就松手的情况。
			 * 如果，用户长按并进行了拖动，那么onTouchEvent就可以捕捉
			 * 到ACTION_MOVE和ACTION_UP等事件进行处理
			 */
			case MotionEvent.ACTION_CANCEL:
			case MotionEvent.ACTION_UP:
				if(mDragView!=null){
					Rect r = mTempRect;
					mDragView.getDrawingRect(r);
					stopDragging();
					unExpandViews(false);
				}
				break;
			}
		}
		return super.onInterceptTouchEvent(ev);
	}

	
	
	
	/*
	 * pointToPosition() doesn't consider invisible views, but we need to, so
	 * implement a slightly different version.
	 */
	private int myPointToPosition(int x, int y) {
		Rect frame = mTempRect;
		final int count = getChildCount();

		for (int i = count - 1; i >= 0; i--) {
			final View child = getChildAt(i);
			child.getHitRect(frame);
			if (frame.contains(x, y)) {
				return getFirstVisiblePosition() + i;
			}
		}
		return INVALID_POSITION;
	}

	private int getItemForPosition(int y) {// 判断item的移动,不能超出
		int adjustedy = y - mDragPoint - 32;
		int pos = myPointToPosition(0, adjustedy);

		if (pos >= 0) {
			if (pos <= mFirstDragPos) {
				pos += 1;
			}
		} else if (adjustedy < 0) {
			pos = 0;
		}
		return pos;
	}

	private void adjustScrollBounds(int y) {
		if (y >= mHeight / 3) {
			mUpperBound = mHeight / 3;
		}
		if (y <= mHeight * 2 / 3) {
			mLowerBound = mHeight * 2 / 3;
		}
	}

	/*
	 * Restore size and visibility for all listitems
	 */
	private void unExpandViews(boolean deletion) {
		for (int i = 0;; i++) {
			View v = getChildAt(i);
			if (v == null) {
				if (deletion) {
					// HACK force update of mItemCount
					int position = getFirstVisiblePosition();
					int y = getChildAt(0).getTop();
					setAdapter(getAdapter());
					setSelectionFromTop(position, y);
					// end hack
				}
				layoutChildren(); // force children to be recreated where needed
				v = getChildAt(i);
				if (v == null) {
					break;
				}
			}
			ViewGroup.LayoutParams params = v.getLayoutParams();
			params.height = mItemHeightNormal;
			v.setLayoutParams(params);
			v.setVisibility(View.VISIBLE);
		}
	}

	/*
	 * Adjust visibility and size to make it appear as though an item is being
	 * dragged around and other items are making room for it: If dropping the
	 * item would result in it still being in the same place, then make the
	 * dragged listitem's size normal, but make the item invisible. Otherwise,
	 * if the dragged listitem is still on screen, make it as small as possible
	 * and expand the item below the insert point. If the dragged item is not on
	 * screen, only expand the item below the current insertpoint.
	 */

	private void doExpansion() {
		// childnum表明了那个item需要给被拖动的item让位
		// 让位是由childnum对应的view将高度调高（设为mItemHeightexpandad）
		// list_item.xml代码需要配合着doExpansion来写才能实现这个功能
		// 如:layout_gravity="botton"这句必须写，才能配合代码onExpansion()
		// 函数实现最好的效果！ 
		int childnum = mDragPos - getFirstVisiblePosition();
		if(mDragPos>mFirstDragPos){
			childnum++;
		}
		
		View first = getChildAt(mFirstDragPos - getFirstVisiblePosition());

		// 在拖动的过程中不断的绘制每个item
		for (int i = 0;; i++) {
			View vv = getChildAt(i);
			if (vv == null) {
				break;
			}
			int height = mItemHeightNormal;
			int visibility = View.VISIBLE;
			// 若vv就是被拖动的item
			if (vv.equals(first)) {
				// 又将被拖动的item托到自己原来的位置
				if (mDragPos == mFirstDragPos) {
					// hovering over the original location
					visibility = View.INVISIBLE;
				} else {// 将被拖动的item托到其他位置
					// not hovering over it
					height = 1;	// 这句必须要写，不清楚为什么
					visibility = View.INVISIBLE;
				}
			} // vv并不是被拖动的item而是需要让开位置的
			else if (i == childnum) {
				if (mDragPos < getCount() -1) {
					height = mItemHeightExpanded;
				}
			}
			ViewGroup.LayoutParams params = vv.getLayoutParams();
			params.height = height;
			vv.setLayoutParams(params);
			vv.setVisibility(visibility);
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		// 有正在拖动的item，并且注册了DragListener或DropListener
		if((mDragListener!=null||mDropListener!=null)&&mDragView!=null){
			int action = ev.getAction();
			switch (action) {
			// 拖动结束,(用户长按item后并真的进行了拖动)
			case MotionEvent.ACTION_UP:
			case MotionEvent.ACTION_CANCEL:
				// 下面这两句必须写，才能完成排序
				Rect r = mTempRect;
				mDragView.getDrawingRect(r);
				stopDragging();
				// 放下拖动的item
				if (mDropListener != null && mDragPos >= 0 && mDragPos < getCount()) {
					mDropListener.drop(mFirstDragPos, mDragPos);
				}
				unExpandViews(false);
				break;
			// 正在拖动
			case MotionEvent.ACTION_DOWN:
			case MotionEvent.ACTION_MOVE:
				int x = (int) ev.getX();
				int y = (int) ev.getY();
				dragView(x, y);
				int itemnum = getItemForPosition(y);
				if (itemnum >= 0) {
					if (action == MotionEvent.ACTION_DOWN || itemnum != mDragPos) {
						if (mDragListener != null) {
							mDragListener.drag(mDragPos, itemnum);
						}
						mDragPos = itemnum;
						doExpansion();
					}
					int speed = 0;
					adjustScrollBounds(y);
					if (y > mLowerBound) {
						// scroll the list up a bit
						speed = y > (mHeight + mLowerBound) / 2 ? 16 : 4;
					} else if (y < mUpperBound) {
						// scroll the list down a bit
						speed = y < mUpperBound / 2 ? -16 : -4;
					}
					if (speed != 0) {
						int ref = pointToPosition(0, mHeight / 2);
						if (ref == AdapterView.INVALID_POSITION) {
							// we hit a divider or an invisible view, check
							// somewhere else
							ref = pointToPosition(0, mHeight / 2
									+ getDividerHeight() + 64);
						}
						View v = getChildAt(ref - getFirstVisiblePosition());
						if (v != null) {
							int pos = v.getTop();
							setSelectionFromTop(ref, pos - speed);
						}
					}
				}
				break;
			}
			return false;
		}
		// 这个返回值能够实现selected的选中效果，如果返回true则无选中效果
		return super.onTouchEvent(ev);
	}

	private void startDragging(Bitmap bm, int y) {
		stopDragging();

		mWindowParams = new WindowManager.LayoutParams();

		// 从上到下计算y方向上的相对位置
		mWindowParams.gravity = Gravity.TOP;
		mWindowParams.x = 0;
		mWindowParams.y = y - mDragPoint + mCoordOffset;

		mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
		mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;

		// 下面这些参数能够帮助准确定位到选中项点击位置
		mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
				| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
				| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
				| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
		mWindowParams.format = PixelFormat.TRANSLUCENT;
		mWindowParams.windowAnimations = 0;

		// 把影像ImagView添加到当前视图中
		ImageView v = new ImageView(getContext());

		v.setBackgroundColor(mDragBackgroundColor);
		v.setImageBitmap(bm);
		mDragBitmap = bm;

		mWindowManager = (WindowManager) getContext().getSystemService("window");
		mWindowManager.addView(v, mWindowParams);

		// 把影像ImageView引用到变量mDragView，用于后续操作(拖动，释放等等)
		mDragView = v;
	}

	private void dragView(int x, int y) {
		// 让拖动中的item透明50%
        mWindowParams.alpha = 0.5f;  
		// 更新y坐标位置
		mWindowParams.y = y - mDragPoint + mCoordOffset;
		// 更新界面
		mWindowManager.updateViewLayout(mDragView, mWindowParams);
	}

	private void stopDragging() {
		if (mDragView != null) {
			WindowManager wm = (WindowManager) getContext().getSystemService("window");
			wm.removeView(mDragView);
			mDragView.setImageDrawable(null);
			mDragView = null;
		}
		if (mDragBitmap != null) {
			mDragBitmap.recycle();
			mDragBitmap = null;
		}
	}

	public void setDragListener(DragListener listener) {
		mDragListener = listener;
	}
	public void setDropListener(DropListener listener) {
		mDropListener = listener;
	}
	public interface DragListener {
		void drag(int from, int to);
	}
	// 恢复被滑到左边的便笺
	public void slideRight(){
		if(mItemSlideLeft==-1)
			return;
		mIsSliding = true;
		// 让被恢复后的item的选项不可点
		ViewGroup itemSlideLeft =  (ViewGroup) getChildAt(mItemSlideLeft-getFirstVisiblePosition());
		ImageView edit = (ImageView)itemSlideLeft.findViewById(R.id.list_option_edit);
		ImageView bgColor = (ImageView)itemSlideLeft.findViewById(R.id.list_option_bgcolor);
		ImageView clock = (ImageView)itemSlideLeft.findViewById(R.id.list_option_clock);
		edit.setClickable(false);
		bgColor.setClickable(false);
		clock.setClickable(false);
		// 向右滑的动画
		Animation anim = AnimationUtils.loadAnimation(getContext(), R.anim.slide_right);
		anim.setFillEnabled(true);
		anim.setFillAfter(true);
		anim.setAnimationListener(new AnimationListener() {
			public void onAnimationStart(Animation animation) {
				mIsSliding = true;
			}
			public void onAnimationRepeat(Animation animation) {}
			public void onAnimationEnd(Animation animation) {
				mIsSliding = false;
				mItemSlideLeft = -1;
			}
		});
		ViewGroup itemShouldMove = (ViewGroup)itemSlideLeft.getChildAt(1);
		itemShouldMove.startAnimation(anim);
	}
	
	/**
	 * 处理各种触摸事件的Listener，主要通过其接口
	 * 来调用NoteDatabase和MyAdapter的方法
	 * 处理的事件：
	 * 1.长按item可以拖动排序，由drop实现最后数据持久化和更新列表
	 * 2.向右滑动item可以删除一条便笺，由drop实现最后数据持久化和更新列表
	 * 3.向左滑动item可以出现相关的四个选项
	 * @author wangchen
	 * 
	 */
	public interface DropListener {
		// 放下被拖动的item的时候调用
		void drop(int from, int to);
		// 删除一条item后调用
		boolean delete(int pos);
		// 点击了一条item的选项后调用
		void handleOptions(int option, int pos);
		
		void displayNote(int pos);
		
		boolean isAskBeforeDelete();
		
	}
}
